#pragma once

#include <process.h>

class CriticalSection
{
public:
	CRITICAL_SECTION m_cs;

	CriticalSection() { ::InitializeCriticalSection(&m_cs); }
	~CriticalSection() { ::DeleteCriticalSection(&m_cs); }
	operator CRITICAL_SECTION() { return m_cs; }
	void Enter() { ::EnterCriticalSection(&m_cs); }
	void Leave() { ::LeaveCriticalSection(&m_cs); }
	void Lock() { Enter(); }
	void Unlock() { Leave(); }
	BOOL TryLock() { return ::TryEnterCriticalSection(&m_cs); }
	BOOL TryEnter() { return ::TryEnterCriticalSection(&m_cs); }
};

class ScopedLock
{
public:
	CriticalSection& m_cs;
	ScopedLock(CriticalSection& cs):m_cs(cs)
	{
		m_cs.Enter();
	};
	~ScopedLock()
	{
		m_cs.Leave();
	}
};

class WaitableHandle
{
public:
	HANDLE m_handle;
	operator HANDLE()
	{ return m_handle; }

	HANDLE GetHandle() const
	{ return m_handle; }

	DWORD WaitFor(DWORD dwMilliseconds=INFINITE)
	{ return ::WaitForSingleObject(m_handle, dwMilliseconds); }

	DWORD MsgWaitFor(DWORD dwMilliseconds=INFINITE)
	{
		DWORD dwWait;
		while (WAIT_OBJECT_0 + 1 == (dwWait = ::MsgWaitForMultipleObjects(1, &m_handle, FALSE, dwMilliseconds, QS_ALLINPUT)))
		{
			// message to process, process and continue
			MSG msg;

			// just to be careful, i've run into an unexplainable problem
			// where the ::MsgWait... returned with this value, but the
			// GetMessage() hung waiting for a message, so just check first
			if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
				DispatchMessage(&msg);
		}
		return dwWait;
	}

	BOOL Close()
	{
		if (NULL == m_handle)
			return FALSE;
		BOOL bRet = ::CloseHandle(m_handle);
		m_handle = NULL;
		return bRet;
	}
};


//
// Manual reset event, requires Reset in order to unsignal the event
//
class ManualEvent: public WaitableHandle
{
public:
	ManualEvent(BOOL bSet=FALSE)
	{
		m_handle = ::CreateEvent(NULL, TRUE, bSet, NULL);
	}
	ManualEvent(LPCTSTR szName, BOOL bSet, LPSECURITY_ATTRIBUTES psa=NULL)
	{
		m_handle = ::CreateEvent(psa, TRUE, bSet, szName);
	}

	~ManualEvent() { Close(); }
	void Set()		{ ::SetEvent(m_handle); }
	void Reset()	{ ::ResetEvent(m_handle); }
	void Pulse()	{ ::PulseEvent(m_handle); }
	BOOL IsSet()	{ return (WAIT_OBJECT_0 == WaitFor(0)); }
};


//
// Auto reset event, the event is reset once a SINGLE waiting thread
// is release from a wait state
//
class AutoEvent: public WaitableHandle
{
public:
	AutoEvent(BOOL bSet=FALSE)
	{
		m_handle = ::CreateEvent(NULL, FALSE, bSet, NULL);
	}
	AutoEvent(LPCTSTR szName, BOOL bSet=FALSE, LPSECURITY_ATTRIBUTES psa=NULL)
	{
		m_handle = ::CreateEvent(psa, FALSE, bSet, szName);
	}

	~AutoEvent()	{   Close(); }
	void Set()		{ ::SetEvent(m_handle); }
	void Reset()	{ ::ResetEvent(m_handle); }
	void Pulse()	{ ::PulseEvent(m_handle); }
};